//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Reflection;
using System.Windows.Controls.Primitives;

namespace Microsoft.Research.ScientificWorkflow.ManagementStudioControls.Common
{
    /// <summary>
    /// A column in the SortListView object that allows the SortProperty and 
    /// SortStyle to be specified in XAML. The SortProperty specifies the 
    /// underlying bound property that is used when sorting and SortStyle
    /// specifies the resource that is used for the column header.
    /// </summary>
    public class SortListViewColumn : GridViewColumn
    {
        /// <summary>
        /// The property on which the column has to be sorted.
        /// </summary>
        public string SortProperty
        {
            get { return (string)GetValue(SortPropertyProperty); }
            set { SetValue(SortPropertyProperty, value); }
        }

        public static readonly DependencyProperty SortPropertyProperty =
            DependencyProperty.Register("SortProperty",
            typeof(string), typeof(SortListViewColumn));

        /// <summary>
        /// A flag to check if the column has to be sorted by default.
        /// </summary>
        public bool SortByDefault
        {
            get { return (bool)GetValue(SortByDefaultProperty); }
            set { SetValue(SortByDefaultProperty, value); }
        }

        public static readonly DependencyProperty SortByDefaultProperty =
            DependencyProperty.Register("SortByDefault",
            typeof(bool), typeof(SortListViewColumn));

        /// <summary>
        /// The default sort direction.
        /// </summary>
        public ListSortDirection DefaultSortDirection
        {
            get { return (ListSortDirection)GetValue(DefaultSortDirectionProperty); }
            set { SetValue(DefaultSortDirectionProperty, value); }
        }

        public static readonly DependencyProperty DefaultSortDirectionProperty =
            DependencyProperty.Register("DefaultSortDirection",
            typeof(ListSortDirection), typeof(SortListViewColumn));

        /// <summary>
        /// Gets or sets the minimum width for this column.
        /// </summary>
        public double MinimumWidth
        {
            get { return (double)GetValue(MinimumWidthProperty); }
            set { SetValue(MinimumWidthProperty, value); }
        }

        /// <summary>
        /// Sets the dependency property for change of the MinimumWidth property.
        /// </summary>
        public static readonly DependencyProperty MinimumWidthProperty =
            DependencyProperty.Register(
                "MinimumWidth",
                typeof(double),
                typeof(SortListViewColumn),
                new FrameworkPropertyMetadata(double.NaN, new PropertyChangedCallback(OnMinimumWidthChanged)));


        /// <summary>
        /// Changes the width of the column, if the column width is less than the minimum sets the minimum as the
        /// width, else sets the input value itself.
        /// </summary>
        /// <param name="sender">
        /// MinimumWidthColumn
        /// </param>
        /// <param name="baseValue">
        /// The actual width
        /// </param>
        /// <returns>
        /// The proper width.
        /// </returns>
        private static object OnCoerceWidth(DependencyObject sender, object baseValue)
        {
            SortListViewColumn column = sender as SortListViewColumn;
            object returnValue = baseValue;

            if (column != null)
            {
                if (!double.IsNaN((double)baseValue))
                {
                    // Return either the ActualWidth or the minimum width, whichever is greater.
                    if ((double)baseValue <= column.MinimumWidth)
                    {
                        returnValue = column.MinimumWidth;
                    }
                }
                else
                {
                    returnValue = column.MinimumWidth;
                }
            }

            return returnValue;
        }

        /// <summary>
        /// Fired upon change of the minimum width.
        /// </summary>
        /// <param name="sender">
        /// MinimumWidth Column
        /// </param>
        /// <param name="e">
        /// DependencyPropertyChangedEventArgs
        /// </param>
        private static void OnMinimumWidthChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
        {
            SortListViewColumn column = sender as SortListViewColumn;
            if (column != null)
            {
                // Forces the Width property to recognize the new value.
                column.CoerceValue(WidthProperty);
            }
        }
    }


    /// <summary>
    /// A ListView control that supports sorting.
    /// </summary>
    public class SortListView : ListView
    {
        /// <summary>
        ///  The current column that is sorted.
        /// </summary>
        private SortListViewColumn sortColumn;

        /// <summary>
        /// The previous column that was sorted.
        /// </summary>
        private SortListViewColumn previousSortColumn;

        /// <summary>
        /// The current direction the header is sorted.
        /// </summary>
        private ListSortDirection sortDirection;

        /// <summary>
        /// The data template for the gridviewcolumn header with an up arrow.
        /// </summary>
        private static string ARROWUPTEMPLATE = "HeaderTemplateArrowUp";

        /// <summary>
        /// The data template for the gridviewcolumn header with a down arrow.
        /// </summary>
        private const string ARROWDOWNTEMPLATE = "HeaderTemplateArrowDown";

        /// <summary>
        /// The data template for the gridviewcolumn header.
        /// </summary>
        private const string NORMALTEMPLATE = "HeaderTemplateTransparent";

        public SortListView()
        {
            this.AddHandler(Thumb.DragDeltaEvent, new DragDeltaEventHandler(Thumb_DragDelta), true);
        }

        static SortListView()
        {
            ItemsSourceProperty.OverrideMetadata(typeof(SortListView), new FrameworkPropertyMetadata(new PropertyChangedCallback(ItemsSourcePropertyChanged)));
        }

        /// <summary>
        /// The event is fired when the items source for the listview is set. Selects the default sort column.
        /// </summary>
        /// <param name="target">The listview.</param>
        /// <param name="args">The event arguments.</param>
        private static void ItemsSourcePropertyChanged(DependencyObject target, DependencyPropertyChangedEventArgs args)
        {
            SortListView targetListView = target as SortListView;
            PerformDefaultSort(targetListView);
        }

        /// <summary>
        /// Perform a sort on the default column.
        /// </summary>
        /// <param name="listView">The listview.</param>
        private static void PerformDefaultSort(SortListView listView)
        {
            // cast the ListView's View to a GridView
            GridView gridView = listView.View as GridView;

            if (gridView != null)
            {
                // Determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.
                SortListViewColumn sortListViewColumn = null;

                foreach (GridViewColumn gridViewColumn in gridView.Columns)
                {
                    sortListViewColumn = gridViewColumn as SortListViewColumn;

                    if (sortListViewColumn != null)
                    {
                        if (sortListViewColumn.SortByDefault)
                        {
                            break;
                        }
                        sortListViewColumn = null;
                    }
                }

                // if the default sort column is defined, sort the data and then update the templates as necessary.
                if (sortListViewColumn != null)
                {
                    // Sort the data.
                    SortListView.SortList(listView.ItemsSource, sortListViewColumn.SortProperty, sortListViewColumn.DefaultSortDirection);

                    // Update the column header based on the sort column and order.
                    SortListView.UpdateHeaderTemplate(listView, null, sortListViewColumn, sortListViewColumn.DefaultSortDirection);
                }
            }
        }

        /// <summary>
        /// Sort the list based on the column.
        /// </summary>
        /// <param name="source">The items source.</param>
        /// <param name="propertyName">The property name on which the list is to be sorted.</param>
        /// <param name="direction">The sort direction.</param>
        private static void SortList(IEnumerable source, string propertyName, ListSortDirection direction)
        {
            if (source != null)
            {
                // Get the data to sort.
                ICollectionView dataView = CollectionViewSource.GetDefaultView(source);

                if (dataView.CurrentItem != null)
                {
                    PropertyInfo property = dataView.CurrentItem.GetType().GetProperty(propertyName);
                    if (property != null)
                    {
                        // If the property to sort on is an enum use the default comparer.
                        if (property.PropertyType.IsEnum)
                        {
                            ListCollectionView view = (ListCollectionView)dataView;
                            view.CustomSort = new DefaultComparer(direction, property);
                            view.Refresh();
                        }
                        else
                        {
                            // Specify the new sorting information.
                            dataView.SortDescriptions.Clear();
                            SortDescription description = new SortDescription(propertyName, direction);
                            dataView.SortDescriptions.Add(description);
                            dataView.Refresh();
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Update the column header based on the sort column and order.
        /// </summary>
        /// <param name="listView">The listview.</param>
        /// <param name="previousSortedColumn">The previous sorted column.</param>
        /// <param name="currentColumn">The column to be sorted.</param>
        /// <param name="sortDirection">The sort direction.</param>
        private static void UpdateHeaderTemplate(SortListView listView, SortListViewColumn previousSortedColumn, SortListViewColumn currentColumn, ListSortDirection sortDirection)
        {
            DataTemplate headerTemplate;

            // Restore the previous header.
            if (previousSortedColumn != null)
            {
                headerTemplate = listView.TryFindResource(NORMALTEMPLATE) as DataTemplate;
                if (headerTemplate != null)
                    previousSortedColumn.HeaderTemplate = headerTemplate;
            }

            // Apply the template according to the sort direction.
            headerTemplate = sortDirection == ListSortDirection.Ascending ? listView.TryFindResource(ARROWUPTEMPLATE) as DataTemplate :
                listView.TryFindResource(ARROWDOWNTEMPLATE) as DataTemplate;

            if (headerTemplate != null)
                currentColumn.HeaderTemplate = headerTemplate;
        }

        /// <summary>
        /// Initialized event handler.
        /// </summary>
        /// <param name="e">Event arguments.</param>
        protected override void OnInitialized(EventArgs e)
        {
            // Handle the event when a header is clicked.
            this.AddHandler(GridViewColumnHeader.ClickEvent, new RoutedEventHandler(OnHeaderClicked));

            this.SelectDefaultColumn();
 
            base.OnInitialized(e);
        }

        /// <summary>
        /// Select the default column to be set as the previous sort column.
        /// </summary>
        private void SelectDefaultColumn()
        {
            // cast the ListView's View to a GridView
            GridView gridView = this.View as GridView;

            if (gridView != null)
            {
                // Determine which column is marked as IsDefaultSortColumn. Stops on the first column marked this way.
                SortListViewColumn sortListViewColumn = null;

                foreach (GridViewColumn gridViewColumn in gridView.Columns)
                {
                    sortListViewColumn = gridViewColumn as SortListViewColumn;

                    if (sortListViewColumn != null)
                    {
                        if (sortListViewColumn.SortByDefault)
                        {
                            break;
                        }
                        sortListViewColumn = null;
                    }
                }

                // if the default sort column is defined, sort the data and then update the templates as necessary.
                if (sortListViewColumn != null)
                {
                    this.sortDirection = sortListViewColumn.DefaultSortDirection;
                    this.previousSortColumn = sortListViewColumn;
                    this.sortColumn = sortListViewColumn;
                }
            }
        }

        /// <summary>
        /// A header was clicked. Sort the associated column.
        /// </summary>
        private void OnHeaderClicked(object sender, RoutedEventArgs e)
        {
            // Make sure the column is really being sorted.
            GridViewColumnHeader header = e.OriginalSource as GridViewColumnHeader;
            if (header == null || header.Role == GridViewColumnHeaderRole.Padding)
                return;

            SortListViewColumn column = header.Column as SortListViewColumn;
            if (column == null || column.SortProperty == null)
                return;

            // See if a new column was clicked, or the same column was clicked.
            if (this.sortColumn != column)
            {
                // A new column was clicked.
                this.previousSortColumn = this.sortColumn;
                this.sortColumn = column;
                this.sortDirection = ListSortDirection.Ascending;
            }
            else
            {
                // The same column was clicked, change the sort order.
                this.previousSortColumn = null;
                this.sortDirection = (sortDirection == ListSortDirection.Ascending) ?
                    ListSortDirection.Descending : ListSortDirection.Ascending;
            }

            // Sort the data.
            SortListView.SortList(this.ItemsSource, column.SortProperty, sortDirection);

            // Update the column header based on the sort column and order.
            SortListView.UpdateHeaderTemplate(this, previousSortColumn, column, sortDirection);

            e.Handled = true;
        }

        void Thumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            Thumb senderAsThumb = e.OriginalSource as Thumb;
            GridViewColumnHeader header
              = senderAsThumb.TemplatedParent as GridViewColumnHeader;
            //header.
            if (header != null)
            {
                SortListViewColumn column = header.Column as SortListViewColumn;
                if ((e.HorizontalChange < 0) && (header.Column.ActualWidth < column.MinimumWidth))
                {
                    header.Column.Width = column.MinimumWidth;
                }
            }
        }
    }

    /// <summary>
    /// Comparer for the listview sort.
    /// </summary>
    public class DefaultComparer : IComparer
    {
        /// <summary>
        /// The sort direction.
        /// </summary>
        ListSortDirection direction;

        /// <summary>
        /// The property to sort on.
        /// </summary>
        PropertyInfo property;

        /// <summary>
        /// Creates a new instance of the comparer.
        /// </summary>
        /// <param name="direction">The sort direction.</param>
        /// <param name="propertyName">The property name.</param>
        public DefaultComparer(ListSortDirection direction, PropertyInfo property)
        {
            this.direction = direction;
            this.property = property;
        }

        #region IComparer Members

        /// <summary>
        /// Compare the objects.
        /// </summary>
        /// <param name="x">The first object to compare.</param>
        /// <param name="y">The second object to compare.</param>
        /// <returns>+ve value if the first object is greater, -ve otherwise.</returns>
        public int Compare(object x, object y)
        {
            if (property == null)
            {
                return 0;
            }
            int result;
            object valueX = property.GetValue(x, null);
            object valueY = property.GetValue(y, null);

            if (direction == ListSortDirection.Ascending)
            {
                result = string.Compare(valueX.ToString(), valueY.ToString());
            }
            else
            {
                result = string.Compare(valueY.ToString(), valueX.ToString());
            }
            return result;
        }

        #endregion
    }
}
